package xserial

import (
	"io"
	"sync"
	"time"

	"github.com/tarm/serial"
)

type SerialSettings struct {
	Device      string       `json:"device,omitempty"`
	Baudrate    int          `json:"baudrate,omitempty"`
	DataBits    int          `json:"dataBits,omitempty"`
	StopBits    StopBitsType `json:"stopBits,omitempty"`
	Parity      Parity       `json:"parity,omitempty"`
	ReadTimeout time.Duration
}

type StopBitsType byte

const (
	Stop1   StopBitsType = 1
	Stop1p5 StopBitsType = 15
	Stop2   StopBitsType = 2
)

type Parity byte

const (
	ParityNone  Parity = 'N'
	ParityOdd   Parity = 'O'
	ParityEven  Parity = 'E'
	ParityMark  Parity = 'M' // parity bit is always 1
	ParitySpace Parity = 'S' // parity bit is always 0
)

type Serial struct {
	locker   *sync.Mutex
	settings SerialSettings
	port     *serial.Port
	// hasTrigger bool
	dataC   chan []byte
	errC    chan error
	stop    bool
	started bool
	count   int
}

func NewSerial(settings SerialSettings) (*Serial, error) {
	cfg := serial.Config{
		Name:        settings.Device,
		Baud:        settings.Baudrate,
		Size:        byte(settings.DataBits),
		Parity:      serial.Parity(settings.Parity),
		StopBits:    serial.StopBits(settings.StopBits),
		ReadTimeout: settings.ReadTimeout,
	}
	h, err := serial.OpenPort(&cfg)
	if err != nil {
		return nil, err
	}
	s := Serial{
		locker:   &sync.Mutex{},
		settings: settings,
		port:     h,
		// hasTrigger: false,
		dataC:   make(chan []byte),
		errC:    make(chan error),
		stop:    false,
		started: false,
		count:   0,
	}
	return &s, nil
}

func (s *Serial) StartRead() error {
	s.locker.Lock()
	defer s.locker.Unlock()

	if !s.started {
		s.started = true
		go s.run()
	}
	return nil
}

func (s *Serial) Ref() {
	// s.locker.Lock()
	// s.locker.Unlock()
	s.count = s.count + 1
}

func (s *Serial) Stop() error {
	s.locker.Lock()
	defer s.locker.Unlock()
	s.count = s.count - 1
	if s.count <= 0 {
		s.stop = true
	}
	return nil
}

func (s *Serial) DataC() chan []byte {
	return s.dataC
}

func (s *Serial) ErrC() chan error {
	return s.errC
}

func (s *Serial) Write(data []byte) (int, error) {
	s.locker.Lock()
	defer s.locker.Unlock()
	return s.port.Write(data)
}

func (s *Serial) run() {
	for {

		data := make([]byte, 1024)
		n, err := s.port.Read(data)
		if s.stop {
			return
		}
		if err != nil {
			if err != io.EOF {
				s.errC <- err
			}
		}
		if n > 0 {
			s.dataC <- data[:n]
		}

	}
}
